package main

import (
	"compress/gzip"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"strings"

	"contrib.go.opencensus.io/exporter/prometheus"
	"github.com/gorilla/mux"
	"github.com/urfave/cli/v2"
	"go.opencensus.io/stats/view"
	"golang.org/x/xerrors"

	logging "github.com/ipfs/go-log/v2"

	"github.com/filecoin-project/go-jsonrpc"
	"github.com/filecoin-project/go-state-types/abi"
	"github.com/filecoin-project/specs-storage/storage"

	manet "github.com/multiformats/go-multiaddr/net"

	"github.com/filecoin-project/lotus/build"
	"github.com/filecoin-project/lotus/lib/lotuslog"
	"github.com/filecoin-project/lotus/metrics"
	"github.com/filecoin-project/lotus/node"
	"github.com/filecoin-project/lotus/node/repo"
	promclient "github.com/prometheus/client_golang/prometheus"
)

var log = logging.Logger("c2")

const FlagC2Repo = "c2-repo"

func main() {
	lotuslog.SetupLogLevels()

	local := []*cli.Command{
		runCmd,
	}

	app := &cli.App{
		Name:    "lotus-c2",
		Usage:   "c2外包",
		Version: build.UserVersion(),
		Flags:   []cli.Flag{},

		Commands: local,
	}
	app.Setup()
	os.Args = append(os.Args, "run")
	if err := app.Run(os.Args); err != nil {
		log.Errorf("%+v", err)
		os.Exit(1)
		return
	}
}

var runCmd = &cli.Command{
	Name:  "run",
	Usage: "Start api server",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:  "listen",
			Usage: "host address and port the api server will listen on",
			Value: "0.0.0.0:2323",
		},
		&cli.StringFlag{
			Name:    FlagC2Repo,
			EnvVars: []string{"LOTUS_C2_PATH"},
			Value:   "~/.c2",
			Usage:   "c2 工作目录",
		},
		&cli.StringFlag{
			Name:    "redis_address",
			EnvVars: []string{"LOTUS_C2_REDIS_ADDRESS"},
			Value:   "127.0.0.1:6379",
			Usage:   "redis 地址",
		},
		&cli.StringFlag{
			Name:    "redis_password",
			EnvVars: []string{"LOTUS_C2_REDIS_PASSWORD"},
			Value:   "123456",
			Usage:   "redis密码",
		},
	},
	Action: func(cctx *cli.Context) error {
		log.Info("Starting lotus c2")

		// Register all metric views
		if err := view.Register(metrics.ChainNodeViews...); err != nil {
			log.Fatalf("Cannot register the view: %v", err)
		}

		repoPath := cctx.String(FlagC2Repo)
		r, err := repo.NewFS(repoPath)
		if err != nil {
			return err
		}

		ok, err := r.Exists()
		if err != nil {
			return err
		}
		if !ok {
			if err := r.Init(repo.C2); err != nil {
				return err
			}
		}

		lr, err := r.Lock(repo.C2)
		if err != nil {
			return err
		}

		defer func() {
			if err := lr.Close(); err != nil {
				log.Error("closing repo", err)
			}
		}()

		var (
			address = cctx.String("listen")
		)

		serverOptions := make([]jsonrpc.ServerOption, 0)
		if maxRequestSize := cctx.Int("api-max-req-size"); maxRequestSize != 0 {
			serverOptions = append(serverOptions, jsonrpc.WithMaxRequestSize(int64(maxRequestSize)))
		}

		log.Info("setting up API endpoint at " + address)

		addr, err := net.ResolveTCPAddr("tcp", address)
		if err != nil {
			return xerrors.Errorf("failed to resolve endpoint address: %w", err)
		}

		maddr, err := manet.FromNetAddr(addr)
		if err != nil {
			return xerrors.Errorf("failed to convert endpoint address to multiaddr: %w", err)
		}
		// 初始化redis
		initRedis(cctx.String("redis_address"), cctx.String("redis_password"))

		ctxM, cancel := context.WithCancel(context.Background())
		// 开启调度
		m := NewManager(ctxM, rdb)
		go m.Run()

		// 注册服务
		ip, err := GetOutBoundIP()
		if err != nil {
			return err
		}
		arr := strings.Split(address, ":")
		arr[0] = ip
		ip = strings.Join(arr, ":")
		s := NewService(CacheKey("info:" + ip))
		closeFun, err := s.Register(ip)
		if err != nil {
			return err
		}

		// 注册路由
		h := mux.NewRouter()

		h.HandleFunc("/proxy/commit2", func(w http.ResponseWriter, r *http.Request) {
			data := &Commit2Request{}
			resp := &Commit2Response{}

			body, err := gzip.NewReader(r.Body)
			if err != nil {
				fmt.Println("unzip is failed, err:", err)
				return
			}
			defer body.Close()
			dataJson, err := ioutil.ReadAll(body)
			if err != nil {
				errFun(w, err, resp)
				return
			}
			err = json.Unmarshal(dataJson, data)
			if err != nil {
				errFun(w, err, resp)
				return
			}
			log.Infof("/proxy/commit2 请求 : 扇区id %s, minerid %s", data.SectorID.Number, data.SectorID.Miner)
			rs, err := s.DoWork(data)
			if err != nil {
				errFun(w, err, resp)
				return
			}
			resp.Proof = rs
			resp.SectorID = data.SectorID

			//返回
			successFun(w, resp)
		})

		//提交任务
		h.HandleFunc("/proxy/task", func(w http.ResponseWriter, r *http.Request) {
			data := &Commit2Request{}
			resp := &Commit2Response{}
			body, err := gzip.NewReader(r.Body)
			if err != nil {
				fmt.Println("unzip is failed, err:", err)
			}
			defer body.Close()
			dataJson, err := ioutil.ReadAll(body)
			json.Unmarshal(dataJson, &data)
			if err != nil {
				errFun(w, err, resp)
				return
			}
			receiveChan := make(chan storage.Proof)
			task := &Commit2Data{Commit2Request: data, ReturnChan: receiveChan}
			log.Infof("/proxy/task 请求 : 扇区id %s, minerid %s", task.SectorID.Number, task.SectorID.Miner)
			m.taskChan <- task
			select {
			case resp.Proof = <-receiveChan:
				successFun(w, resp)
				return
			case <-ctxM.Done():
				errFun(w, errors.New("程序退出"), resp)
				return
			}

		})

		registry := promclient.DefaultRegisterer.(*promclient.Registry)
		exporter, err := prometheus.NewExporter(prometheus.Options{
			Registry:  registry,
			Namespace: "lotus_c2",
		})
		if err != nil {
			return err
		}
		h.Handle("/debug/metrics", exporter)
		h.PathPrefix("/").Handler(http.DefaultServeMux)

		stopFunc, err := node.ServeRPC(h, "lotus-c2", maddr)
		if err != nil {
			return xerrors.Errorf("failed to serve rpc endpoint: %w", err)
		}

		<-node.MonitorShutdown(nil, node.ShutdownHandler{
			Component: "rpc",
			StopFunc:  stopFunc,
		}, node.ShutdownHandler{
			Component: "c2service",
			StopFunc:  closeFun,
		}, node.ShutdownHandler{
			Component: "manage",
			StopFunc: func(ctx context.Context) error {
				fmt.Printf("关闭")
				cancel()
				return nil
			},
		})
		return nil
	},
}

type Commit2Request struct {
	SectorID   abi.SectorID
	Commit1Out storage.Commit1Out
}
type Commit2Response struct {
	SectorID abi.SectorID
	Proof    storage.Proof
}

// errFun 500
func errFun(rw http.ResponseWriter, err error, resp interface{}) {
	rw.WriteHeader(http.StatusInternalServerError)
	log.Warnf("err ,%s", err)
	json.NewEncoder(rw).Encode(resp)
}

// successFun 200
func successFun(rw http.ResponseWriter, resp interface{}) {
	log.Infof("Commit2Response ,%+v", resp)
	rw.WriteHeader(http.StatusOK)
	json.NewEncoder(rw).Encode(resp)
}

// GetOutBoundIP 获取本机ip
func GetOutBoundIP() (ip string, err error) {
	conn, err := net.Dial("udp", "8.8.8.8:53")
	if err != nil {
		fmt.Println(err)
		return
	}
	localAddr := conn.LocalAddr().(*net.UDPAddr)
	// fmt.Println(localAddr.String())
	ip = strings.Split(localAddr.String(), ":")[0]
	return
}
